home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-07-15 | 18.2 KB | 617 lines | [TEXT/MPS ] |
- #include "ArrangeCallbacks.h"
- #include "PluginLibrary.h"
-
- #include <Dialogs.h>
- #include <Windows.h>
- #include <Resources.h>
- #include <Memory.h>
- #include <Events.h>
-
- // AutoCompletion.cp - based on GenericPlugin.cp as of 7/15/94
-
-
- #define ModuleRsrcID 0xFFFF8000
-
- #define baseCmdCode 0x12000100 // replace this with a value obtained from
- // Common Knowledge.
-
- #define aboutCmdCode (baseCmdCode + 0)
-
- #define autoCompletionAction 0x11000006
-
-
- class Plugin
- {
- public:
- Plugin(const ArrangeCallbackTbl* theCalls);
- ~Plugin();
-
- arHookResult ClickNotify( arClickLocation loc, Point where, Short modifiers,
- Short clickCount, arNoteID note, arFieldID field,
- arPathID path );
- arHookResult KeyNotify ( Short theChar, Short key, Short modifiers );
- arHookResult MenuNotify ( Integer commandCode, Integer commandParam,
- Short modifiers );
- arHookResult FieldNotify( arNoteID note, arFieldID field, arFieldAction action,
- const char* choiceText );
- void TopicNotify( arTopicID newTopic, arWindowID newWindow,
- arTopicAction action );
- void TickNotify ( );
- arHookResult FileNotify ( arFileAction action );
- arHookResult QuitNotify ( );
- void ATMNotify ( );
-
- private:
- const ArrangeCallbackTbl* calls; // callback table
-
- arNoteID selNote; // Note containing the current selection; nil if there
- // is no active text selection, or if the selected
- // field is not a popup field.
-
- // These variables are only valid if selNote is non-nil.
- arFieldID selField; // Field in which the selection is made.
- Integer lastKeyTime; // TickCount when the user last typed a key.
- Boolean lastKeyWasBackspace; // True if last key event was a backspace.
- Boolean textIsDirty; // True if the user has made any editing
- // change since the last call to RunCompletion.
-
- void EnterField(arNoteID note, arFieldID field);
- void ExitField();
- void NoteUserAction(Boolean isBackspace, Boolean isDirty);
- void Tick();
- void RunCompletion();
-
- Boolean FindPopupMatch( const char* matchText, Integer matchTextLen,
- const char* popupText, Integer popupTextLen,
- OUT const char* &matchStart, OUT Integer &matchLen );
- }; // Plugin
-
-
- /*************************************************************************/
- /**************************** Main entry point ***************************/
- /*************************************************************************/
-
- /* Root entry point for the module - must be the first function in the
- * file, so that the linker will place it first in the code segment.
- * This is the routine which Arrange calls at application startup time
- * (and again at quit time).
- *
- * Most plug-ins will not need to modify this routine or the Hook functions
- * immediately following. All customization can be done in the "Plugin"
- * section (below).
- */
- extern "C" Integer OurModuleRoot( ModuleParamBlock *pb, ModuleRootAction action,
- Integer /*p1*/ )
- {
- /* Extract the callback table from our parameter block, and coerce it
- * to the extended Arrange table.
- */
- ArrangeCallbackTbl* calls = (ArrangeCallbackTbl*)(pb->calls);
-
- switch (action)
- {
- case mrLoad:
- {
- // Allocate memory and create a new Plugin object.
- void* storage = calls->mem->AllocMem( sizeof(Plugin),
- amFreeStore | amErrIfNoMem );
- pb->moduleRefcon = uInteger(new(storage) Plugin(calls));
- return 1;
- }
-
- case mrUnload:
- {
- /* Dispose of the Plugin object and release the memory
- * it occupies.
- */
- delete (Plugin*)(pb->moduleRefcon);
- calls->mem->DeallocMem((void*)(pb->moduleRefcon), amFreeStore);
- return 0;
- }
-
- case mrFindEntry:
- default:
- {
- /* This module contains no extended entry points, so we always
- * return 0 for the mrFindEntry message. Most plug-ins will not
- * use extended entry points.
- */
- return 0;
- }
-
- } // switch (action)
-
- } // OurModuleRoot
-
-
- /* These hook functions simply pass control to the appropriate function
- * in the Plugin object.
- */
- static arHookResult OurClickHook( ModuleParamBlock* pb, arClickLocation loc,
- Point where, pShort modifiers, pShort clickCount,
- arNoteID note, arFieldID field,
- arPathID path ENDP )
- {
- return ((Plugin*)(pb->moduleRefcon))->ClickNotify( loc, where, modifiers,
- clickCount, note, field,
- path );
- }
-
-
- static arHookResult OurKeyHook( ModuleParamBlock* pb, pShort theChar, pShort key,
- pShort modifiers ENDP )
- {
- return ((Plugin*)(pb->moduleRefcon))->KeyNotify(theChar, key, modifiers);
- }
-
-
- static arHookResult OurMenuHook( ModuleParamBlock* pb, Integer commandCode,
- Integer commandParam, pShort modifiers ENDP )
- {
- return ((Plugin*)(pb->moduleRefcon))->MenuNotify( commandCode, commandParam,
- modifiers );
- }
-
-
- static arHookResult OurFieldHook( ModuleParamBlock* pb, arNoteID note,
- arFieldID field, arFieldAction action,
- const char* choiceText ENDP )
- {
- return ((Plugin*)(pb->moduleRefcon))->FieldNotify( note, field, action,
- choiceText );
- }
-
-
- static void OurTopicHook( ModuleParamBlock* pb, arTopicID newTopic,
- arWindowID newWindow, arTopicAction action ENDP )
- {
- ((Plugin*)(pb->moduleRefcon))->TopicNotify(newTopic, newWindow, action);
- }
-
-
- static void OurTickHook(ModuleParamBlock* pb ENDP)
- {
- ((Plugin*)(pb->moduleRefcon))->TickNotify();
- }
-
-
- static arHookResult OurFileHook(ModuleParamBlock* pb, arFileAction action ENDP)
- {
- return ((Plugin*)(pb->moduleRefcon))->FileNotify(action);
- }
-
-
- static arHookResult OurQuitHook(ModuleParamBlock* pb ENDP)
- {
- return ((Plugin*)(pb->moduleRefcon))->QuitNotify();
- }
-
-
- static void OurATMHook(ModuleParamBlock* pb ENDP)
- {
- ((Plugin*)(pb->moduleRefcon))->ATMNotify();
- }
-
-
- /*************************************************************************/
- /********************************* Plugin ********************************/
- /*************************************************************************/
-
- /* Number of ticks after a standard keydown event before we try to complete
- * the text.
- */
- #define completionDelay 15
-
-
- /* Number of ticks after a backspace event before we try to complete the
- * text.
- */
- #define backspaceDelay 60
-
-
- /* Construct a Plugin object. This is called once, from the OurModuleRoot
- * function, when the module is initialized (i.e. at application startup time).
- */
- Plugin::Plugin(const ArrangeCallbackTbl* theCalls)
- {
- // Record the callback table for future use.
- calls = theCalls;
-
- /* These commands, if un-commented-out, register this plugin to be called
- * by Arrange when various events occur. For example, if you un-comment-out
- * the call to SetClickHook, then Plugin::ClickNotify will be called
- * whenever the user clicks in any of the locations described in the
- * arClickLocation enum.
- */
- // calls->ui->SetClickHook(OurClickHook, 0, true);
- // calls->ui->SetKeyHook (OurKeyHook, 0, true, charFilter, keyFilter, modFilter);
- // calls->ui->SetMenuHook (OurMenuHook, 0, true, whichCommand);
- // calls->ui->SetFieldHook(OurFieldHook, 0, true, whichField);
- // calls->ui->SetTopicHook(OurTopicHook, 0, true);
- calls->ui->SetTickHook (OurTickHook, 0, true);
- // calls->ui->SetFileHook (OurFileHook, 0, true);
- // calls->ui->SetQuitHook (OurQuitHook, 0, true);
- // calls->ui->SetATMHook (OurATMHook, 0, true);
-
- calls->ui->SetFieldHook(OurFieldHook, 0, true, 0);
-
- calls->ui->AddMenuItem(mPluginAbout, "About Auto-completion", 0, aboutCmdCode, 0);
- calls->ui->SetMenuHook(OurMenuHook, 0, true, aboutCmdCode);
-
- calls->ui->RegisterFieldProc( "Auto-completion", autoCompletionAction, fpAction,
- 1 << arFTPopup, OurFieldHook, autoCompletionAction );
-
- selNote = nil;
- } // Plugin constructor
-
-
- /* Dispose of a Plugin object. This is called when Arrange terminates. You
- * should free up any data structures which you allocation in the Plugin
- * constructor (above).
- */
- Plugin::~Plugin()
- {
- } // ~Plugin
-
-
- /* This function is called whenever the user clicks in an "interesting place"
- * in a document window. It should return true if we handle the click, false
- * (the normal case) when the click should be passed along to other plug-ins
- * or to Arrange's normal event processing. See the documentation for
- * SetClickHook for more details.
- */
- arHookResult Plugin::ClickNotify( arClickLocation /*loc*/, Point /*where*/,
- Short /*modifiers*/, Short /*clickCount*/,
- arNoteID /*note*/, arFieldID /*field*/,
- arPathID /*path*/ )
- {
- return false; // Let Arrange handle the event
- } // ClickNotify
-
-
- /* This function is called whenever the user types a key which matches the
- * filters we pass to SetKeyHook in Plugin's constructor. It should return
- * true if we handle the event, false (the normal case) when the event
- * should be passed along to other plug-ins or to Arrange's normal event
- * processing. See the documentation for SetKeyHook for more details.
- */
- arHookResult Plugin::KeyNotify( Short /*theChar*/, Short /*key*/,
- Short /*modifiers*/ )
- {
- return false; // Let Arrange handle the event
- } // KeyNotify
-
-
- /* This function is called whenever the user chooses a menu item for which
- * we have registered (via the SetMenuHook function). It should return true
- * if we handle the command, false (the normal case) when the command should
- * be passed along to other plug-ins or to Arrange's normal event processing.
- * See the documentation for SetMenuHook for more details.
- */
- arHookResult Plugin::MenuNotify( Integer commandCode, Integer /*commandParam*/,
- Short /*modifiers*/ )
- {
- /* If this is our About menu item, display our about-box dialog and return
- * true to indicate we handled the command.
- */
- if (commandCode == aboutCmdCode)
- {
- Alert(ModuleRsrcID, nil);
- return true;
- }
- else
- return false; // Let Arrange handle the event
-
- } // MenuNotify
-
-
- /* If we register to recieve events for a field by calling SetFieldHook, this
- * function will be called for any events in that field. It should return
- * false in almost all cases. See the documentation for SetFieldHook for
- * more details.
- */
- arHookResult Plugin::FieldNotify( arNoteID note, arFieldID field,
- arFieldAction action,
- const char* /*choiceText*/ )
- {
- switch (action)
- {
- case faEntry:
- EnterField(note,field);
- break;
-
- case faExit:
- ExitField();
- break;
-
- case faUpdate:
- RunCompletion();
- break;
-
- case faKeystroke:
- case faEditCmd:
- NoteUserAction(false, true);
- break;
-
- case faBackspace:
- NoteUserAction(true, true);
- break;
-
- case faSelChange:
- NoteUserAction(false, false);
- break;
-
- } // switch (action)
-
- return false;
- } // FieldNotify
-
-
- /* This function is called whenever the user switches windows or changes
- * the current folder/topic/view in the front window. See the documentation
- * for SetTopicHook for more details.
- */
- void Plugin::TopicNotify( arTopicID /*newTopic*/, arWindowID /*newWindow*/,
- arTopicAction /*action*/ )
- {
- } // TopicNotify
-
-
- /* This function is called periodically, whenever Arrange recieves a null
- * event from the Event Manager.
- */
- void Plugin::TickNotify()
- {
- /* Exit if there is no active selection in a popup field, or if the
- * user hasn't typed anything for us to complete.
- */
- if (selNote == nil || !textIsDirty)
- return;
-
- /* Determine whether enough time has elapsed since the last user
- * activity.
- */
- Integer delay;
- if (lastKeyWasBackspace)
- delay = backspaceDelay;
- else
- delay = completionDelay;
-
- /* If enough time has elapsed, then attempt to complete what the user
- * has typed.
- */
- if (TickCount() >= lastKeyTime + delay)
- RunCompletion();
-
- } // TickNotify
-
-
- /* This function is called whenever various file-level events occur. It
- * should return true in almost all cases. See the documentation for
- * SetFileHook for more details.
- */
- arHookResult Plugin::FileNotify(arFileAction /*action*/)
- {
- return true;
- } // FileNotify
-
-
- /* This function is called if the user voluntarily quits Arrange. It should
- * return true to allow the user to quit, false to prevent it. See the
- * documentation for SetQuitHook for more details.
- */
- arHookResult Plugin::QuitNotify()
- {
- return true;
- } // QuitNotify
-
-
- /* This function is called whenever the user clicks in the menu bar or types
- * a command key, just before processing the event. It should do any fixing
- * up of menus which might be necessary based on the current state of affairs.
- * See the documentation for SetATMHook for more details.
- */
- void Plugin::ATMNotify()
- {
- } // ATMNotify
-
-
- // This function is called when the user clicks in a field of some note.
- void Plugin::EnterField(arNoteID note, arFieldID field)
- {
- /* If the field is a popup field, and the Auto-completion action is selected
- * for that field, set our selNote and selField variables accordingly;
- * otherwise set selNote to nil.
- */
- arFieldInfo fieldInfo;
- fieldInfo.versNum = 1;
- calls->sysObj->GetFieldInfo(field, &fieldInfo);
-
- if ( fieldInfo.type == arFTPopup &&
- calls->ui->TestFieldProc(field, fpAction, autoCompletionAction) )
- {
- selNote = note;
- selField = field;
- lastKeyTime = Integer(TickCount());
- lastKeyWasBackspace = false;
- textIsDirty = false;
- }
- else
- selNote = nil;
-
- } // EnterField
-
-
- // This function is called when the user clicks out of a field.
- void Plugin::ExitField()
- {
- selNote = nil;
- } // ExitField
-
-
- /* This function is called when the user clicks, types, or takes other
- * action in the active field. isBackspace is true if the event is a press
- * of the backspace key or equivalent, false otherwise (we wait longer after
- * a backspace than after other keys to finish what the user is typing).
- * isDirty is true if the action is one that changes the contents of the
- * field (i.e. typing or cut/paste but not Copy, a mouse click, or an arrow
- * key).
- */
- void Plugin::NoteUserAction(Boolean isBackspace, Boolean isDirty)
- {
- lastKeyTime = Integer(TickCount());
- lastKeyWasBackspace = isBackspace;
- textIsDirty = textIsDirty || isDirty;
- } // NoteUserAction
-
-
- /* Search through the list of entries in popupText for one which has a prefix
- * equal (up to case differences) to matchText. If we find such an entry,
- * place its address in matchStart and its length in matchLen and return true.
- * Otherwise, return false (in which case matchStart and matchLen are
- * undefined).
- */
- Boolean Plugin::FindPopupMatch( const char* matchText, Integer matchTextLen,
- const char* popupText, Integer popupTextLen,
- OUT const char* &matchStart,
- OUT Integer &matchLen )
- {
- // Loop until the list of popup entries has been exhausted.
- while (popupTextLen > 0)
- {
- // Find the length of the current entry.
- Integer curEntryLen = 0;
- while (curEntryLen < popupTextLen && popupText[curEntryLen] != '\n')
- curEntryLen++;
-
- // Check whether this entry is a match.
- if (curEntryLen >= matchTextLen)
- {
- char matchBuf[1], popupBuf[1];
-
- Boolean isAMatch = true;
- for (Integer i=0; i<matchTextLen; i++)
- {
- matchBuf[0] = matchText[i];
- popupBuf[0] = popupText[i];
- calls->util->LowerString(matchBuf, 1);
- calls->util->LowerString(popupBuf, 1);
- if (matchBuf[0] != popupBuf[0])
- {
- isAMatch = false;
- break;
- }
-
- }
-
- if (isAMatch)
- {
- matchStart = popupText;
- matchLen = curEntryLen;
- return true;
- }
-
- } // if (curEntryLen >= matchTextLen)
-
- // Advance to the next entry.
- popupText += curEntryLen + 1;
- popupTextLen -= curEntryLen + 1;
- } // while (popupTextLen > 0)
-
- return false;
- } // FindPopupMatch
-
-
- /* Scan the text in the active field to see if we can finish what the user
- * has typed by inserting an entry from the popup list. This is called when
- * someone tries to flush the contents of the field, or from our Tick method.
- *
- * FUTURE NOTE: need to modify this routine to not just silently fail when
- * text lengths exceed 2000 characters. Either allocate a dynamic buffer,
- * or beep, or something.
- */
- void Plugin::RunCompletion()
- {
- // Exit if the user hasn't typed anything for us to complete.
- if (selNote == nil || !textIsDirty)
- return;
-
- textIsDirty = false;
-
- /* Get the current contents of the active field. Exit if they are
- * too large to fit in our buffer.
- */
- char textBuf[2000];
- Integer selStart,selEnd;
- Integer textLen = calls->sel->GetSelText( sizeof(textBuf), textBuf,
- &selStart, &selEnd );
- if (textLen >= sizeof(textBuf))
- return;
-
- /* Find the start of the "last entry" in the text. This is all text after
- * the last comma character, if any, and excluding any leading spaces.
- */
- char* scanPtr = textBuf;
- char* stopPtr = textBuf + textLen;
- char* entryStart = textBuf;
- while (scanPtr < stopPtr)
- {
- if (*scanPtr == ',')
- entryStart = scanPtr+1;
-
- scanPtr++;
- }
-
- while (*entryStart == ' ' || *entryStart == '\t' || *entryStart == '\n')
- entryStart++;
-
- // Exit if no text has been entered yet.
- if (entryStart >= textBuf + textLen)
- return;
-
- /* Get the list of entries in the popup menu for this field. Remember
- * that this list is stored in what is nominally the "default value"
- * field of the field definition. Exit if the text is too long to hold
- * in our buffer.
- */
- char popupBuf[2000];
- arFieldID defaultValueField = arFieldID( calls->sysObj->
- GetBuiltInObject(boDefaultValueField) );
- Integer popupTextLen = calls->data->GetFieldText( selField, defaultValueField,
- sizeof(popupBuf), popupBuf );
- if (popupTextLen >= sizeof(popupBuf))
- return;
-
- /* Search for a match between the user's text and an entry in the popup
- * menu.
- */
- const char* matchStart;
- Integer matchLen;
- Integer entryPos = entryStart - textBuf;
- if (!FindPopupMatch( entryStart, textLen - entryPos,
- popupBuf, popupTextLen,
- matchStart, matchLen ))
- return;
-
- /* We found a match; exit if it is too large to append onto our text
- * buffer.
- */
- if (entryPos + matchLen >= sizeof(textBuf))
- return;
-
- /* Finish what the user has typed with the entry from the popup menu, and
- * select the text we are inserting.
- */
- Integer copyLen = matchLen - (textLen - entryPos);
- if (copyLen > 0)
- {
- selStart = textLen;
- selEnd = selStart + copyLen;
- for (Integer i=0; i<copyLen; i++)
- textBuf[textLen+i] = matchStart[textLen-entryPos+i];
-
- calls->sel->SetSelText(textBuf, textLen+copyLen, selStart, selEnd);
- }
-
- } // RunCompletion
-